﻿package extremefx.ui {
	import extremefx.conditions.requiresNumber;	
	import extremefx.IDisposable;
	import extremefx.events.EventHandler;
	import extremefx.events.KeyEventArgs;
	import extremefx.geom.Size;
	
	import flash.display.Graphics;
	import flash.display.LineScaleMode;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.utils.Dictionary;		

	/**
	 * @author Marcelo Volmaro
	 */
	public class Component implements IDisposable {
		private static var _count:uint = 0;
		private var _ef:Boolean;
		
		public static var componentList:Dictionary = new Dictionary(true);
		
		protected var _disposing:Boolean;
		protected var _tabEnabled:Boolean;
		
		protected static var _oldFocused:UIComponent;
		
		protected var _parent:Container;
		protected var _dirty:Boolean;
		protected var _width:uint;
		protected var _height:uint;
		
		protected var _fwidth:Boolean;
		protected var _fheight:Boolean;
		
		protected var _x:uint;
		protected var _y:uint;
		
		protected var _minSize:Size;
		protected var _maxSize:Size;
		protected var _prefSize:Size;
		protected var _name:String;
		
		internal var _visualUI:Sprite;
		
		public function Component() {
			_name = "efxcmp_"+(_count++);
			
			var spr:Sprite = _visualUI = new Sprite(); 
			spr.addEventListener(Event.ADDED_TO_STAGE, _addedToStageListener, false, 0, true);
			spr.addEventListener(Event.REMOVED_FROM_STAGE, _removedFromStageListener, false, 0, true);
			
			spr.focusRect = 
			spr.tabChildren = 
			spr.useHandCursor = 
			spr.tabEnabled = 
			spr.buttonMode = false;
			spr.tabIndex = -1;

			spr.addEventListener(Event.UNLOAD, dispose, false, 0, true);
			
			componentList[spr] = this;
			
//			var accessProps:AccessibilityProperties = new AccessibilityProperties();
//			accessProps.name
//			
//			if (Capabilities.hasAccessibility) {
//			    Accessibility.updateProperties();
//			}
		}
		
		internal function tabEnabled(pEnabled:Boolean):void {
			_tabEnabled = pEnabled;
			
			if (!_parent) return;
			
			var tg:TabGroup = _parent._containerTabGroup;
			if (!tg) return;
			
			if (pEnabled){
				tg.addComponent(this);
				
			} else {
				tg.removeComponent(this);
			}
		}

		public function get parent():Container {
			return _parent;
		}

		public function set parent(pParent:Container):void {
			_parent = pParent;
			tabEnabled(_tabEnabled && (pParent is Container));
		}

//		protected function getMousePosition():Point {
//			var mx:int = int(_visualUI.mouseX);
//			var my:int = int(_visualUI.mouseY);
//			
//			if (-1 < mx && mx < _width && -1 < my && my < _height) return new Point(mx, my);
//			return null;
//		}
	
		private function _redraw(ev:Event = null):void {
			_visualUI.removeEventListener(Event.ENTER_FRAME, _redraw);
			_ef = false;
			
			if (_dirty) {
				_draw();
				_dirty = false;
				if (_eOnRedraw) _eOnRedraw.fire();
			}
		}

		/**
		 * Sets both the size and the position of the component at once.
		 */
		public function setBounds(pX:int, pY:int, pW:uint = 0, pH:uint = 0):void {
			var shouldInvalidate:Boolean = false;
			if (pX != _x || pY != _y) {
				_visualUI.x = _x = pX;
				_visualUI.y = _y = pY;
				
				_onPositionChange();
				shouldInvalidate = true;
			}
			
			if ((pW && pH) && (pW != _width || pH != _height)) {
				if (!_fwidth) _width = pW;
				if (!_fheight) _height = pH;
				_onSizeChange();
				shouldInvalidate = true;
			}
			
			if (shouldInvalidate) invalidate();
		}

		/**
		 * Gets the bounds of this component in the form of a Rectangle object.
		 * If you pass a rectangle as an argument, the function stores the bounds of this component into that rectangle and will return the same rectangle.
		 */
		public function getBounds(pBounds:Rectangle = null):Rectangle {
			if (!pBounds) {
				pBounds = new Rectangle();
			}
			
			pBounds.left = _x;
			pBounds.top = _y;
			pBounds.width = _width;
			pBounds.height = _height;
			return pBounds;
		}

		/**
		 * invalidates the component. Forces a redraw on the next stage draw cicle.
		 */
		protected function invalidate():void {
			_dirty = true;
			UIStage.STAGE.invalidate();
			if (_visualUI.parent && !_ef) {
				_visualUI.addEventListener(Event.ENTER_FRAME, _redraw, false, 0, true);
				_ef = true;
			}
		}

		
		/**
		 * Abstract. Should be overridden by extending classes.
		 * This functions is called when the component needs to be re-painted
		 */
		protected function _draw():void {}

		/**
		 * Abstract. May be overriden in a derived class so the component knows when it was added to the stage.
		 */
		protected function _onAddedToStage(ev:Event):void {}
		protected function _onRemovedFromStage(ev:Event):void {}
		protected function _onPositionChange():void {	}
		protected function _onSizeChange():void {}
		protected function _keyDownHandler(pSender:KeyHandler, pArgs:KeyEventArgs):void{}
		protected function _keyUpHandler(pSender:KeyHandler, pArgs:KeyEventArgs):void{}

		public function dispose():void {
			if (_disposing) return;
			_disposing = true;
			
			delete componentList[_visualUI];
			
			if (_parent) _parent.remove(this);
			if (_visualUI.parent) _visualUI.parent.removeChild(_visualUI);
			
			_visualUI.removeEventListener(Event.ADDED_TO_STAGE, _addedToStageListener);
			_visualUI.removeEventListener(Event.REMOVED_FROM_STAGE, _removedFromStageListener);
			_visualUI.removeEventListener(Event.RENDER, _redraw);
			_visualUI.removeEventListener(Event.ENTER_FRAME, _redraw);

			if (_eOnRedraw) _eOnRedraw.dispose();
		}

		/**
		 * Gets the mininimum size of this component.
		 */
		public function get minimumSize():Size {
			return _minSize;
		}

		/**
		 * Sets the minimum size of this component to a constant value.
		 */
		public function set minimumSize(pSize:Size):void {
			_minSize = pSize;
		}
		
		/**
		 * Gets the maximum size of this component.
		 */
		
		public function get maximumSize():Size {
			return _maxSize;
		}
		
		public function set maximumSize(pSize:Size):void {
			_maxSize = pSize;
		}

		/**
		 * Gets the preferred size of this component.
		 */
		public function get preferredSize():Size {
			return _prefSize;
		}

		/**
		 * Sets the preferred size of this component to a constant value.
		 */
		public function set preferredSize(pSize:Size):void {
			_prefSize = pSize;
		}
		
		public function get position():Point {
			return new Point(_x, _y);
		}
		
		public function get x():int {
			return _x;
		}
		
		public function get y():int {
			return _y;
		}
		
		public function set position(pPos:Point):void{
			_visualUI.x = _x = pPos.x;
			_visualUI.y = _y = pPos.y;
			_onPositionChange();
			invalidate();
		}

		public function get size():Size {
			return new Size(_width, _height);
		}
		
		public function get width():uint {
			return _width;
		}
		
		public function get height():uint {
			return _height;
		}

		public function set size(pSize:Size):void {
			if (pSize == null){
				pSize = preferredSize;
			}
			
			if (pSize is Size) {
//				var tmpSize:Size;
//				//check lower bounds
//				if ((tmpSize = minimumSize)){
//					if (pSize.width < tmpSize.width) pSize.width = tmpSize.width;
//					if (pSize.height < tmpSize.height) pSize.height = tmpSize.height;
//				}
//				
//				//check upper bounds
//				if ((tmpSize = maximumSize)){
//					if (tmpSize.width < pSize.width) pSize.width = tmpSize.width;
//					if (tmpSize.height < pSize.height) pSize.height = tmpSize.height;
//				}

				var w:int = pSize.width;
				var h:int = pSize.height;
				
				requiresNumber(w, "width").isGreaterThan(0);
				requiresNumber(h, "height").isGreaterThan(0);
				
				if (w == _width && h == _height) return;
				
				if (!_fwidth) _width = w;
				if (!_fheight) _height = h;
				
				_onSizeChange();
				invalidate();
			}
		}

//		protected function get cursor():String {
//			return "";
//		}

//		protected function set cursor(pCursor:String):void {
//		/**
//		 * A custom mouse cursor should have this properties in order to work correctly:
//		 * buttonMode = true;
//		 * mouseChildren = false;
//		 */
//		}

		protected function _drawBBox(pDO:Sprite, pX:int, pY:int, pW:uint, pH:uint):void {
			var cg:Graphics = pDO.graphics;
			cg.clear();
			
			cg.beginFill(0, 0);
			cg.lineStyle(0, 0, 0, true, LineScaleMode.NONE);
			cg.moveTo(pX, pY);
			cg.lineTo(pW-pX, pY);
			cg.lineTo(pW-pX, pH-pY);
			cg.lineTo(pX, pH-pY);
			cg.lineTo(pX, pY);
			cg.endFill();
		}

		private function _addedToStageListener(ev:Event):void {
			// listener removed to avoid Flash Player bug: "Event.ADDED_TO_STAGE fires twice"
			// see: http://bugs.adobe.com/jira/browse/FP-1569
			_visualUI.removeEventListener(Event.ADDED_TO_STAGE, _addedToStageListener);
			_onAddedToStage(ev);
			_visualUI.addEventListener(Event.RENDER, _redraw, false, 0, true);
		}

		private function _removedFromStageListener(ev:Event):void {
			_onRemovedFromStage(ev);
			_visualUI.removeEventListener(Event.RENDER, _redraw);
			dispose();
		}
		
		private var _eOnRedraw:EventHandler;
		public function get onRedraw():EventHandler {
			if (_eOnRedraw == null) _eOnRedraw = new EventHandler(this);
			return _eOnRedraw;
		}
		
		protected var _kind:String;
		public function toString() : String {
			return "extremefx.ui."+_kind+"::"+_name;
		}
		
		public function get name():String {
			return _name;
		}
		
		public function set name(name:String):void {
			_name = name;
		}
		
		public function get sprite():Sprite {
			return _visualUI;
		}
	}
}
